package cn.lg.soar.common.algorithm;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;

import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;

import static cn.lg.soar.common.algorithm.SM2Encoder.EC_DOMAIN_PARAMETERS;
import static cn.lg.soar.common.algorithm.SM2Encoder.SM2EC_PARAMETERS;
import static cn.lg.soar.common.util.data.DataUtil.BASE64DE;
import static cn.lg.soar.common.util.data.DataUtil.BASE64EN;

/**
 * 非对称加密工具（国密）
 * @author luguoxiang
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public class SM2Utils {

    private static volatile Key staticKey;

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 加密
     * @param publicKey 公钥
     * @param data 数据
     * @param mode 加密模式
     * @return
     */
    public static byte[] encrypt(byte[] publicKey, byte[] data, SM2Engine.Mode mode) {
        try {
            //提取公钥点
            ECPoint ecPoint = SM2EC_PARAMETERS.getCurve().decodePoint(publicKey);
            // 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
            ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(ecPoint, EC_DOMAIN_PARAMETERS);
            SM2Engine sm2Engine = new SM2Engine(mode);
            sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
            return sm2Engine.processBlock(data, 0, data.length);
        } catch (InvalidCipherTextException e) {
            throw new AlgorithmException(e);
        }
    }

    /**
     * 加密
     * @param publicKey 公钥
     * @param data 数据
     * @return
     */
    public static byte[] encrypt(byte[] publicKey, byte[] data) {
        return encrypt(publicKey, data, SM2Engine.Mode.C1C3C2);
    }

    /**
     * 加密（输出base64）
     * @param publicKey 公钥
     * @param data 数据
     * @param mode 加密模式
     * @return
     */
    public String encryptToBase64(byte[] publicKey, byte[] data, SM2Engine.Mode mode) {
        return BASE64EN.encodeToString(encrypt(publicKey, data, mode));
    }

    /**
     * 加密（输出base64）
     * @param publicKey 公钥
     * @param data 数据
     * @return
     */
    public String encryptToBase64(byte[] publicKey, byte[] data) {
        return BASE64EN.encodeToString(encrypt(publicKey, data));
    }

    /**
     * 解密
     * @param privateKey 私钥
     * @param data 数据
     * @param mode 模式
     * @return
     */
    public static byte[] decrypt(byte[] privateKey, byte[] data, SM2Engine.Mode mode) {
        try {
            ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(new BigInteger(privateKey), EC_DOMAIN_PARAMETERS);
            SM2Engine sm2Engine = new SM2Engine(mode);
            sm2Engine.init(false, privateKeyParameters);
            return sm2Engine.processBlock(data, 0, data.length);
        } catch (InvalidCipherTextException e) {
            throw new AlgorithmException(e);
        }
    }

    /**
     * 解密
     * @param privateKey 私钥
     * @param data 数据
     * @return
     */
    public static byte[] decrypt(byte[] privateKey, byte[] data) {
        return decrypt(privateKey, data, SM2Engine.Mode.C1C3C2);
    }

    /**
     * 解密（输入base64）
     * @param privateKey 私钥
     * @param data 数据（base64）
     * @param mode 模式
     * @return
     */
    public byte[] decryptOfBase64(byte[] privateKey, String data, SM2Engine.Mode mode) {
        return decrypt(privateKey, BASE64DE.decode(data), mode);
    }

    /**
     * 解密（输入base64）
     * @param privateKey 私钥
     * @param data 数据（base64）
     * @return
     */
    public byte[] decryptOfBase64(byte[] privateKey, String data) {
        return decrypt(privateKey, BASE64DE.decode(data));
    }

    /**
     * 生成加密器
     * @param publicKey 公钥
     * @param mode 加密模式
     * @return
     */
    public static SM2Encoder encoder(byte[] publicKey, SM2Engine.Mode mode) {
        return new SM2Encoder(publicKey, mode);
    }

    /**
     * 生成加密器
     * @param publicKey 公钥
     * @return
     */
    public static SM2Encoder encoder(byte[] publicKey) {
        return new SM2Encoder(publicKey);
    }

    /**
     * 生成解码器
     * @param privateKey 私钥
     * @param mode 加密模式
     * @return
     */
    public static SM2Decoder decoder(byte[] privateKey, SM2Engine.Mode mode) {
        return new SM2Decoder(privateKey, mode);
    }

    /**
     * 生成解码器
     * @param privateKey 私钥
     * @return
     */
    public static SM2Decoder decoder(byte[] privateKey) {
        return new SM2Decoder(privateKey);
    }

    /**
     * 构建密钥对
     * @return 构建完的公钥私钥
     * @throws NoSuchAlgorithmException
     */
    public static Key createKey() throws NoSuchAlgorithmException {
        ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
        keyPairGenerator.init(new ECKeyGenerationParameters(EC_DOMAIN_PARAMETERS, SecureRandom.getInstance("SHA1PRNG")));
        AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
        BigInteger privateKey = ((ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate()).getD();

        // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04
        ECPoint ecPoint = ((ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic()).getQ();
        return new Key(ecPoint.getEncoded(false), privateKey.toByteArray());
    }

    /**
     * 获取静态公钥
     * @return
     */
    public static byte[] getPublicKey() {
        return SM2Utils.getStaticKey().getPublicKey();
    }

    /**
     * 获取静态私钥
     * @return
     */
    public static byte[] getPrivateKey() {
        return SM2Utils.getStaticKey().getPrivateKey();
    }

    /**
     * 获取静态密钥，密钥一旦生成就不会变，除非服务停止
     * @return
     */
    private static Key getStaticKey() {
        if (SM2Utils.staticKey == null) {
            synchronized (SM2Utils.class) {
                if (SM2Utils.staticKey == null) {
                    try {
                        SM2Utils.staticKey = SM2Utils.createKey();
                    } catch (NoSuchAlgorithmException e) {
                        throw new AlgorithmException(e);
                    }
                }
            }
        }
        return SM2Utils.staticKey;
    }

    private SM2Utils() {}

    public static class Key {

        private final byte[] privateKey;

        private final byte[] publicKey;

        private Key(byte[] publicKey, byte[] privateKey){
            this.privateKey = privateKey;
            this.publicKey = publicKey;
        }

        public byte[] getPrivateKey() {
            return this.privateKey;
        }

        public byte[] getPublicKey() {
            return this.publicKey;
        }

    }
}
